/* @vitest-environment node */ import { describe, it, expect, vi, beforeEach } from "vitest"; vi.mock("@/lib/auth/session", () => ({ getSession: vi.fn(), })); vi.mock("@/lib/db", () => ({ getDb: vi.fn(), })); vi.mock("@/models/user", () => { const USER_ROLES = Object.freeze({ BRANCH: "branch", ADMIN: "admin", SUPERADMIN: "superadmin", DEV: "dev", }); return { default: { findById: vi.fn(), findOne: vi.fn(), }, USER_ROLES, }; }); import { getSession } from "@/lib/auth/session"; import { getDb } from "@/lib/db"; import User from "@/models/user"; import { PATCH, dynamic } from "./route.js"; function createRequestStub(body) { return { async json() { return body; }, }; } describe("PATCH /api/admin/users/[userId]", () => { beforeEach(() => { vi.clearAllMocks(); getDb.mockResolvedValue({}); }); it('exports dynamic="force-dynamic"', () => { expect(dynamic).toBe("force-dynamic"); }); it("returns 401 when unauthenticated", async () => { getSession.mockResolvedValue(null); const res = await PATCH(createRequestStub({}), { params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }), }); expect(res.status).toBe(401); expect(await res.json()).toEqual({ error: { message: "Unauthorized", code: "AUTH_UNAUTHENTICATED" }, }); }); it("returns 403 when authenticated but not allowed (admin)", async () => { getSession.mockResolvedValue({ userId: "u1", role: "admin", branchId: null, email: "admin@example.com", }); const res = await PATCH(createRequestStub({ email: "x@y.de" }), { params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }), }); expect(res.status).toBe(403); expect(await res.json()).toEqual({ error: { message: "Forbidden", code: "AUTH_FORBIDDEN_USER_MANAGEMENT", }, }); }); it("returns 400 when JSON parsing fails", async () => { getSession.mockResolvedValue({ userId: "u2", role: "superadmin", branchId: null, email: "superadmin@example.com", }); const req = { json: vi.fn().mockRejectedValue(new Error("invalid json")) }; const res = await PATCH(req, { params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }), }); expect(res.status).toBe(400); expect(await res.json()).toEqual({ error: { message: "Invalid request body", code: "VALIDATION_INVALID_JSON", }, }); }); it("returns 400 when body is not an object", async () => { getSession.mockResolvedValue({ userId: "u2", role: "superadmin", branchId: null, email: "superadmin@example.com", }); const res = await PATCH(createRequestStub("nope"), { params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }), }); expect(res.status).toBe(400); expect(await res.json()).toEqual({ error: { message: "Invalid request body", code: "VALIDATION_INVALID_BODY", }, }); }); it("returns 400 when userId param is missing", async () => { getSession.mockResolvedValue({ userId: "u2", role: "dev", branchId: null, email: "dev@example.com", }); const res = await PATCH(createRequestStub({ email: "x@y.de" }), { params: Promise.resolve({ userId: undefined }), }); expect(res.status).toBe(400); expect(await res.json()).toEqual({ error: { message: "Missing required route parameter(s)", code: "VALIDATION_MISSING_PARAM", details: { params: ["userId"] }, }, }); }); it("returns 400 when userId param is invalid", async () => { getSession.mockResolvedValue({ userId: "u2", role: "dev", branchId: null, email: "dev@example.com", }); const res = await PATCH(createRequestStub({ email: "x@y.de" }), { params: Promise.resolve({ userId: "nope" }), }); expect(res.status).toBe(400); expect(await res.json()).toMatchObject({ error: { code: "VALIDATION_INVALID_FIELD" }, }); }); it("returns 404 when user does not exist", async () => { getSession.mockResolvedValue({ userId: "u2", role: "superadmin", branchId: null, email: "superadmin@example.com", }); User.findById.mockReturnValue({ exec: vi.fn().mockResolvedValue(null), }); const res = await PATCH(createRequestStub({ email: "x@y.de" }), { params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }), }); expect(res.status).toBe(404); expect(await res.json()).toEqual({ error: { message: "Not found", code: "USER_NOT_FOUND", details: { userId: "507f1f77bcf86cd799439011" }, }, }); }); it("returns 400 when switching to role=branch without branchId (existing has none)", async () => { getSession.mockResolvedValue({ userId: "u2", role: "dev", branchId: null, email: "dev@example.com", }); const user = { _id: "507f1f77bcf86cd799439011", username: "x", email: "x@example.com", role: "admin", branchId: null, mustChangePassword: false, createdAt: new Date(), updatedAt: new Date(), save: vi.fn(), }; User.findById.mockReturnValue({ exec: vi.fn().mockResolvedValue(user), }); const res = await PATCH(createRequestStub({ role: "branch" }), { params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }), }); expect(res.status).toBe(400); expect(await res.json()).toEqual({ error: { message: "Missing required fields", code: "VALIDATION_MISSING_FIELD", details: { fields: ["branchId"] }, }, }); }); it("returns 200 and updates fields; clears branchId for non-branch roles", async () => { getSession.mockResolvedValue({ userId: "u2", role: "superadmin", branchId: null, email: "superadmin@example.com", }); const user = { _id: "507f1f77bcf86cd799439011", username: "olduser", email: "old@example.com", role: "branch", branchId: "NL01", mustChangePassword: true, createdAt: new Date("2026-02-01T10:00:00.000Z"), updatedAt: new Date("2026-02-02T10:00:00.000Z"), save: vi.fn().mockResolvedValue(true), }; User.findById.mockReturnValue({ exec: vi.fn().mockResolvedValue(user), }); // No uniqueness checks needed here (we only change role + mustChangePassword) const res = await PATCH( createRequestStub({ role: "admin", mustChangePassword: false, }), { params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }), }, ); expect(res.status).toBe(200); // Role changed => branchId must be cleared expect(user.role).toBe("admin"); expect(user.branchId).toBe(null); expect(user.mustChangePassword).toBe(false); expect(user.save).toHaveBeenCalledTimes(1); const body = await res.json(); expect(body).toMatchObject({ ok: true, user: { id: "507f1f77bcf86cd799439011", username: "olduser", email: "old@example.com", role: "admin", branchId: null, mustChangePassword: false, }, }); }); it("returns 400 when username is already taken by another user", async () => { getSession.mockResolvedValue({ userId: "u2", role: "dev", branchId: null, email: "dev@example.com", }); const user = { _id: "507f1f77bcf86cd799439011", username: "olduser", email: "old@example.com", role: "admin", branchId: null, mustChangePassword: false, createdAt: new Date(), updatedAt: new Date(), save: vi.fn(), }; User.findById.mockReturnValue({ exec: vi.fn().mockResolvedValue(user), }); User.findOne.mockReturnValue({ select: vi.fn().mockReturnThis(), exec: vi.fn().mockResolvedValue({ _id: "507f1f77bcf86cd799439099" }), }); const res = await PATCH(createRequestStub({ username: "TakenUser" }), { params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }), }); expect(res.status).toBe(400); const body = await res.json(); expect(body).toEqual({ error: { message: "Username already exists", code: "VALIDATION_INVALID_FIELD", details: { field: "username", value: "takenuser" }, }, }); }); });